﻿using Malee;
using System;
using UnityEngine;
using System.Text.RegularExpressions;
using System.Linq;
using System.IO;
using System.Collections.Generic;
#region Editor 扩展
#if UNITY_EDITOR
using UnityEditor;
[CustomEditor(typeof(EventCodeConfiguration))]
class EventCodeConfigurationEditor : Editor
{
    EventCodeConfiguration configuration = null;
    string searchstr = string.Empty;
    string resultstr = string.Empty;
    private void OnDisable()
    {
        string searchstr = string.Empty;
        string resultstr = string.Empty;
    }
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        configuration = configuration ?? target as EventCodeConfiguration;
        //只有原始名称下或者需要合并的列表有数据时，才会显示按钮，保证汇总性质的配置文件更凸显
        if (configuration.name == configuration.GetType().Name || (null != configuration.ConfigurationThoseNeedBeCombined && configuration.ConfigurationThoseNeedBeCombined.Count > 0))
        {
            GUILayout.Space(20);
            if (GUILayout.Button("生成网络事件码枚举"))
            {
                configuration.GenerateNetworkEventEnum();
            }
            GUILayout.Space(20);

            EditorGUILayout.LabelField("查询网络事件码：");
            EditorGUILayout.BeginHorizontal();
            searchstr = EditorGUILayout.TextField(searchstr);
            GUI.enabled = !string.IsNullOrEmpty(searchstr);
            if (GUILayout.Button("查询"))
            {
                resultstr = configuration.QueryEventCode(searchstr);
            }
            GUI.enabled = true;
            EditorGUILayout.EndHorizontal();

            GUI.enabled = !string.IsNullOrEmpty(resultstr);
            GUILayout.Space(10);
            GUILayout.Label(resultstr);
            GUILayout.Space(10);
            GUI.enabled = true;

            GUILayout.Space(30);
            if (GUILayout.Button("汇总数据并分发到其他配置"))
            {
                var str = string.Join("\n", configuration.ConfigurationThoseNeedBeCombined.Select(v => v.name));
                if (EditorUtility.DisplayDialog("警告：", $"你确认要汇总并反向填充到以下配置：{str}", "OK", "Cancel"))
                {
                    configuration.CloneConfigurationData();
                }
            }
        }
        GUILayout.Space(10);
        GUILayout.Label(@"配置网络事件响应码和请求码
1. [生成网络事件码枚举]按钮在每次新增数据和git pull 冲突时点击。
2. [汇总数据并分发] 按钮主要用于反向填充数据，用于整理初始数据。
3. 生成枚举时发生报错时会中断文件的生成，必须修改无误后重新生成枚举
4. 配置的事件名称不区分大小写
5. 小技巧，修改数据后只有焦点从文本框移出来数据才会被序列化保存。");
        GUILayout.Space(10);
    }
}
#endif
#endregion

[CreateAssetMenu(fileName = "EventCodeConfiguration", menuName = "Configration/Network/EventCodeConfiguration")]
public class EventCodeConfiguration : ScriptableObject
{

    [Reorderable]
    public EventCodePairArray EventCodePairs = new EventCodePairArray();
    [Reorderable]
    public EventCodeConfigurationArray ConfigurationThoseNeedBeCombined = new EventCodeConfigurationArray();

    #region 代码生成预设数据
    const string path = "../Model/Enum";
    const string scriptContext = @"namespace zFrame.Networks
{
    public enum #0 : int
    {
        #1
    }
}";
    #endregion

    #region 创建请求和响应码枚举
    /// <summary>
    /// 生成请求码
    /// </summary>
    public void GenerateNetworkEventEnum()
    {
        /*
         * step 1 名称为空或者重复的条目，中断操作并输出警告
         * step 2  重复的条目，中断并输出警告
         * step 3  Code 格式不匹配 0x123456 模式，中断炒作并输出警告
         * step 4  拼接枚举项，
         * step  5 分配文件夹并写入数据
         * step  6 ping 操作，高亮这个文件
        */
        var pairs = CombineConfigurationData();
        var requestcodes = pairs.Where(x => !string.IsNullOrEmpty(x.name) && !string.IsNullOrEmpty(x.RequestCode))
            .Select(x => $"{(string.IsNullOrEmpty(x.description) ? "" : $"/// <summary>{x.description}</summary>\n\t\t")}{x.name} = {x.RequestCode}")
            .Aggregate((lt, rt) => $"{lt},\n\t\t{rt}");
        GenerateText("RequestCode", requestcodes);

        var responseCode = pairs.Where(x => !string.IsNullOrEmpty(x.name) && !string.IsNullOrEmpty(x.ResponseCode))
        .Select(x => $"{(string.IsNullOrEmpty(x.description) ? "" : $"/// <summary>{x.description}</summary>\n\t\t")}{x.name} = {x.ResponseCode}")
        .Aggregate((lt, rt) => $"{lt},\n\t\t{rt}");
        GenerateText("ResponseCode", responseCode);

        Debug.Log("网络事件码创建成功！");
    }

    /// <summary>
    /// 将配置文件数据反向填充到被合并的配置对象中
    /// </summary>
    public void CloneConfigurationData()
    {
        //数据校验，避免填充无效数据
        var pairs = CombineConfigurationData();
        foreach (var item in ConfigurationThoseNeedBeCombined)
        {
            if (null != item && this != item)
            {
                item.EventCodePairs = pairs;
            }
#if UNITY_EDITOR
            EditorUtility.SetDirty(item);
#endif
        }
        Debug.Log("配置文件反向填充完成！");
    }

    /// <summary>
    /// 合并配置数据
    /// 直接生成合并到临时列表再生成，不需要先合并
    /// </summary>
    private EventCodePairArray CombineConfigurationData()
    {
        //1. 本地数据自校验
        string errorHead = "配置文件合并失败";
        this.CodePairNameValidate(() => errorHead);
        this.CodePairRequestCodeValidate(() => errorHead);
        this.CodePairResponseCodeValidate(() => errorHead);
        if (ConfigurationThoseNeedBeCombined.Count() == 0) return this.EventCodePairs;

        //2. 初始化临时列表，收入本地数据
        EventCodePairArray pairs = new EventCodePairArray();
        pairs.AddRange(EventCodePairs);

        //3. 整合非本实例配置
        foreach (var item in ConfigurationThoseNeedBeCombined)
        {
            if (item == this)
            {
                Debug.LogWarning("待合并的列表中包含了本配置实体，不能自己合并自己！");
                continue;
            }
            if (null == item)
            {
                Debug.LogWarning($"待合并的列表中包含了空值！");
                continue;
            }

            //4. 合并前先完成各自数据校验，避免无谓做功
            item.CodePairNameValidate(() => errorHead);
            item.CodePairRequestCodeValidate(() => errorHead);
            item.CodePairResponseCodeValidate(() => errorHead);


            //5. 轮询合并每一项
            for (int i = 0; i < item.EventCodePairs.Count; i++)
            {
                var pair = item.EventCodePairs[i];
                int index = pairs.FindIndex(v => v.name.Trim().ToLower() == pair.name.Trim().ToLower());
                if (index == -1) //如果不存在则添加
                {
                    pairs.Add(pair);
                }
                else //如果名字存在的也需要校验，则需要确认是不是同名但请求码或者验证码不一样的
                {
                    // 开始校验请求码
                    var otherReq = pair.RequestCode.Trim().ToLower();
                    var thisReq = pairs[index].RequestCode.Trim().ToLower();
                    if (thisReq != otherReq)
                    {
                        string msg = $"【合并数据失败】 配置 {pairs[index].configName} 存在与配置 {item.name} 第 {i} 项 {pair.name} 名称相同但请求码不同的异常!";
                        throw new InvalidDataException(msg); //抛错以便中断操作
                    }

                    // 开始校验响应码
                    var otherResp = pair.ResponseCode.Trim().ToLower();
                    var thisResp = pairs[index].ResponseCode.Trim().ToLower();
                    if (thisResp != otherResp)
                    {
                        string msg = $"【合并数据失败】 配置 {pairs[index].configName} 存在与配置 {item.name} 第 {i} 项 {pair.name} 名称相同但响应码不同的异常!";
                        throw new InvalidDataException(msg); //抛错以便中断操作
                    }
                }
            }

            //6. 完成列表合并，必须再次校验新的数据（可能事件名不同但是请求码或者响应码的情况 ）
            this.CodePairRequestCodeValidate(ref pairs, () => errorHead);
            this.CodePairResponseCodeValidate(ref pairs, () => errorHead);
        }
        return pairs;
    }

    private void OnDisable()
    {
#if UNITY_EDITOR
        EditorUtility.SetDirty(this);
#endif
    }

    #endregion
    #region Assistant Function

    /// <summary>
    ///  事件对的名称校验：不能为空，不能发生重复
    /// </summary>
    public void CodePairNameValidate(Func<string> callback)
    {
        int index = EventCodePairs.FindIndex(v => string.IsNullOrEmpty(v.name.Trim()));
        if (index != -1)
        {
            string msg = $"【{callback?.Invoke()} 】事件名为空！配置 {name} 第 {index} 组数据未指定事件对名称 !";
            throw new InvalidDataException(msg); //抛错以便中断操作
        }

        var list = EventCodePairs.Where(v => !string.IsNullOrEmpty(v.name.Trim())) //先去意外的空值
              .GroupBy(x => x.name.Trim().ToLower(), y => y)
              .Where(v => v.Count() > 1)
              .Select(v => v.ToList())
              .ToList();

        if (list.Count > 0)
        {
            string info = string.Empty;
            foreach (var item in list)
            {
                int firstIndex = EventCodePairs.IndexOf(item[0]);
                foreach (var isd in item)
                {
                    int otherIndex = EventCodePairs.IndexOf(isd);
                    if (otherIndex != firstIndex)
                    {
                        info += $"\n第 {otherIndex} 项与 第{firstIndex} 项 {item[0].name} 重复！";
                    }
                }
            }
            string msg = $"【{callback?.Invoke()}】配置 {name} 事件对名称发现重复！ ↓ 详细信息 ↓ {info}";
            throw new InvalidDataException(msg); //抛错以便中断操作
        }
    }

    /// <summary>
    ///  请求码校验：可空，但不能发生重复
    /// </summary>
    public void CodePairRequestCodeValidate(Func<string> callback)
    {
        EventCodePairs.SetConfigurationInfo(name);//埋入配置来源信息方便异常时输出详细的差异数据
        CodePairRequestCodeValidate(ref EventCodePairs, callback);
    }

    public void CodePairRequestCodeValidate(ref EventCodePairArray target, Func<string> callback)
    {

        var list = target.Where(v =>
        {
            if (!string.IsNullOrEmpty(v.RequestCode.Trim()) && !Regex.IsMatch(v.RequestCode.Trim().ToLower(), @"^0x[0-9a-f]{6}$"))
            {
                throw new InvalidDataException($"【{callback?.Invoke()} 】 配置 {v.configName} 事件对 {v.name} 的请求码 {v.RequestCode} 格式不正确 ！");
            }
            return !string.IsNullOrEmpty(v.RequestCode.Trim());
        }) //先去意外的空值，并穿插做一下格式校验
             .GroupBy(x => x.RequestCode.Trim().ToLower(), y => y)
             .Where(v => v.Count() > 1)
             .Select(v => v.ToList())
             .ToList();

        if (list.Count > 0)
        {
            string info = string.Empty;
            foreach (var item in list)
            {
                int firstIndex = target.IndexOf(item[0]);
                foreach (var isd in item)
                {
                    int otherIndex = target.IndexOf(isd);
                    if (otherIndex != firstIndex)
                    {
                        info += $"\n配置{isd.configName} 中事件 {isd.name} 与 配置{item[0].configName} 中的事件 {item[0].name} 的请求码{item[0].RequestCode} 重复！";
                    }
                }
            }
            string msg = $"【{callback?.Invoke()} 】请求码发现重复 ！ ↓ 详细信息 ↓ {info}";
            throw new InvalidDataException(msg); //抛错以便中断操作
        }
    }

    /// <summary>
    ///  响应码校验：不能发生重复
    /// </summary>
    public void CodePairResponseCodeValidate(Func<string> callback)
    {
        EventCodePairs.SetConfigurationInfo(name);//埋入配置来源信息方便异常时输出详细的差异数据
        CodePairResponseCodeValidate(ref EventCodePairs, callback);
    }

    public void CodePairResponseCodeValidate(ref EventCodePairArray target, Func<string> callback)
    {
        var list = target.Where(v =>
        {
            if (!string.IsNullOrEmpty(v.ResponseCode.Trim()) && !Regex.IsMatch(v.ResponseCode.Trim().ToLower(), @"^0x[0-9a-f]{6}$"))
            {
                throw new InvalidDataException($"【{callback?.Invoke()} 】 配置 {v.configName} 中事件 {v.name} 的请求码 {v.ResponseCode} 格式不正确 ！");
            }
            return !string.IsNullOrEmpty(v.ResponseCode.Trim());
        }) //先去意外的空值，并穿插做一下格式校验
     .GroupBy(x => x.ResponseCode.Trim().ToLower(), y => y)
     .Where(v => v.Count() > 1)
     .Select(v => v.ToList())
     .ToList();

        if (list.Count > 0)
        {
            callback?.Invoke();
            string info = string.Empty;
            foreach (var item in list)
            {
                int firstIndex = target.IndexOf(item[0]);
                foreach (var isd in item)
                {
                    int otherIndex = target.IndexOf(isd);
                    if (otherIndex != firstIndex)
                    {
                        info += $"\n\t配置{isd.configName} 中事件 {isd.name} 与 配置{item[0].configName} 中的事件 {item[0].name} 的响应码 {item[0].ResponseCode} 重复！";
                    }
                }
            }
            string msg = $"【{callback?.Invoke()} 】响应码发现重复！ ↓ 详细信息 ↓ {info}";
            throw new InvalidDataException(msg); //抛错以便中断操作
        }
    }


    /// <summary>
    /// 构建枚举文件
    /// </summary>
    /// <param name="file"></param>
    /// <param name="code"></param>
    private void GenerateText(string file, string code)
    {
        string scrPath = FolderMaker.AllocatePath(this, path, true);
        scrPath = Path.Combine(scrPath, file + ".cs");
        string codee = scriptContext.Replace("#0", file);
        codee = codee.Replace("#1", code);
        using (StreamWriter writer = File.CreateText(scrPath))
        {
            writer.WriteLine(codee);
        }
#if UNITY_EDITOR
        AssetDatabase.Refresh();
        EditorGUIUtility.PingObject(AssetDatabase.LoadAssetAtPath<TextAsset>(scrPath));
#endif
    }

    internal string QueryEventCode(string searchstr)
    {
        searchstr = searchstr.Trim();
        if (!Regex.IsMatch(searchstr, @"^(0[xX])?[0-9a-fA-F]{6}$"))
        {
            throw new InvalidDataException($"事件码 {searchstr} 格式不正确,查询无法进行 ！");
        }
        if (!searchstr.ToLower().StartsWith("0x"))
        {
            searchstr = "0x" + searchstr;
        }

        Func<EventCodeConfiguration, string> Search = (target) =>
        {
            string result = string.Empty;
            int index = target.EventCodePairs.FindIndex(v => v.RequestCode.Equals(searchstr, StringComparison.OrdinalIgnoreCase) || v.ResponseCode.Equals(searchstr, StringComparison.OrdinalIgnoreCase));
            if (index != -1)
            {
                EventCodePair codepair = target.EventCodePairs[index];
                //配置文件： xxxx
                //事件名称：xxxx
                //匹配到请求码/响应码
                result = $@"
配置文件：{target.name}
事件名称：{codepair.name}
匹配详情：匹配到{(codepair.RequestCode.Equals(searchstr, StringComparison.OrdinalIgnoreCase) ? "请求" : "响应")}码 {searchstr}
事件备注：{(string.IsNullOrEmpty(codepair.description) ? "缺省" : codepair.description)}
";
            }
            return result;
        };
        List<EventCodeConfiguration> configs = new List<EventCodeConfiguration>(ConfigurationThoseNeedBeCombined);
        configs.Add(this);
        string str = string.Empty;
        foreach (var item in configs.Distinct())
        {
            str += Search(item);
        }
        if (!string.IsNullOrEmpty(str)) return "发现匹配数据！\n" + str;

        return $"事件码 {searchstr} 未录入！";
    }
    #endregion

    #region Assistant Class
    [Serializable]
    public class EventCodePair
    {
        /// <summary>
        ///  事件对名称 
        /// </summary>
        [Tooltip("请务必指定不重名的名称！")]
        public string name;
        /// <summary>
        /// 请求码 ,形如 0x000001 的16进制写法
        /// </summary>
        [Tooltip("请务必使用形如 0x000001 的16进制写法！")]
        public string RequestCode;
        /// <summary>
        /// 响应码,形如 0x000001 的16进制写法
        /// </summary>
        [Tooltip("请务必使用形如 0x000001 的16进制写法！")]
        public string ResponseCode;
        /// <summary>
        ///  一些必要的备注
        /// </summary>
        [Tooltip("按需备注")]
        public string description;
        /// <summary>
        /// 所在的配置文件名称，用于异常输出
        /// </summary>
        [HideInInspector]
        public string configName;
    }
    [Serializable]
    public class EventCodePairArray : ReorderableArray<EventCodePair>
    {
        public void SetConfigurationInfo(string name)
        {
            foreach (var item in this)
            {
                item.configName = name;
            }
        }
    }
    [Serializable] public class EventCodeConfigurationArray : ReorderableArray<EventCodeConfiguration> { }
    #endregion
}
